Skip to content

前言

字数
4862 字
阅读时间
21 分钟

略.

确定当前内核开启调试

在调试前我们需要确定该系统是否开启了支持内核调试:

shell
# 使用如下命令
zcat /proc/config.gz

# 确认是否有如下配置:
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y
CONFIG_GDB_SCRIPTS=y
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
shell
# 部分版本需要依赖于下面的开启,具体需要使用make nenuconfig查看依赖情况
CONFIG_DEBUG_INFO_DWARF5=y
# 或者
CONFIG_DEBUG_INFO_DWARF4=y

或者:

shell
### 以下几个方式都可以###

# 查看是否有任何信息
dmesg | grep -i kgdb

# 挂载debugfs,然后在看看debug下是否有KGDB相关文件
mount -t debugfs none /sys/kernel/debug
ls /proc/sys/kernel/debug

# 查看模块
lsmod | grep kgdboc

获取源码

如果(非嵌入式)设备没有开启KGDB的话,我们需要编译源码来开启。 这里主要指的是如ubuntu系统

下载

shell
sudo apt search linux-source
uname -r
# 寻找一个合适的源码包

sudo apt install linux-source-5.10.160 #版本选自己合适的

解压源码:

shell
# 解压
cd /usr/src/linux-source-xxx
tar -xvf linux-source-xxx

# 处理,如果有debain等目录在其中,我们需要把解压的内容移动到上一层目录中
mv linux-source-xxx/* ./

# 复制本系统的配置到源码中
cp -v /boot/config-$(uname -r) .config

检查是否开启了上面的KGDB配置:

shell
# 期间的错误,可以安装下面的软件(报什么错,就安装什么)
sudo apt install libncurses-dev

# /bin/sh: 1: flex: not found,解决如下:
sudo apt-get install flex

# /bin/sh: 1: bison: not found,解决如下:
sudo apt-get install bison

# 查看配置
make menuconfig

# 实际上我看到服务器版本是默认开启的,后面编译可以跳过

编译:

shell
# 编译时会有些头文件没有
sudo apt install libelf-dev libssl-dev bc pahole
make -j$(npro)
sudo make modules_install
sudo make install
sudo update-grub

配置进入调试

进入调试分两类:

  • 启动时进入
  • 运行时进入

启动时进入

启动就进入调试模式有两种方式:

  1. 修改/etc/default/grub
  2. 开机进入高级设置

方法1

这需要修改CMD LINE

shell
sudo vim /etc/default/grub
# 写入:
#GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX_DEFAULT="kgdboc=ttyXXXX,115200 kgdbwait"

方法2

在开机时,按住shift,然后进入高级设置,再次按下e,然后在配置中加入:

shell
kgdboc=ttyXXXX,115200 kgdbwait

运行时进入

方法1

shell
echo "ttyXXX" > /sys/module/kgdboc/parameters/kgdboc

当然有些系统可能没有这个文件夹,你需要编译源码时开启KGDBOC。

方法2

shell
# 查看是否开启了sysrq
echo 1 | sudo tee /proc/sys/kernel/sysrq

# 查看它支持的事件
cat /proc/sys/kernel/sysrq

# 进入调试KGDB
echo g > /proc/sysrq-trigger

主机设备配置

我们需要将目标机上源码编译好的vmlinux复制到另外一台设备上,然后使用:

shell
sudo gdb vmlinux
(gdb) set serail baud 115200
(gdb) target remote /dev/ttyS0

调试操作

调试的话有两种:

  • 使用host进行远程gdb
  • 调试串口进行KGDB调试

远程调试kgdb

查看^39124e 这种调试与gdb操作无异

本地串口调试

进入调试后状态如下: 我们使用help就可以看到命令列表:

shell
Command         Usage                Description
----------------------------------------------------------
md              <vaddr>             Display Memory Contents, also mdWcN, e.g. md8c1
mdr             <vaddr> <bytes>     Display Raw Memory
mdp             <paddr> <bytes>     Display Physical Memory
mds             <vaddr>             Display Memory Symbolically
mm              <vaddr> <contents>  Modify Memory Contents
go              [<vaddr>]           Continue Execution
rd                                  Display Registers
rm              <reg> <contents>    Modify Registers
ef              <vaddr>             Display exception frame
bt              [<vaddr>]           Stack traceback
btp             <pid>               Display stack for process <pid>
bta             [D|R|S|T|C|Z|E|U|I|M|A]
                                    Backtrace all processes matching state flag
btc                                 Backtrace current process on each cpu
btt             <vaddr>             Backtrace process given its struct task address
env                                 Show environment variables
set                                 Set environment variables
help                                Display Help Message
?                                   Display Help Message
cpu             <cpunum>            Switch to new cpu
kgdb                                Enter kgdb mode
ps              [<flags>|A]         Display active task list
pid             <pidnum>            Switch to another task
reboot                              Reboot the machine immediately
lsmod                               List loaded kernel modules
sr              <key>               Magic SysRq key
dmesg           [lines]             Display syslog buffer
defcmd          name "usage" "help" Define a set of commands, down to endefcmd
kill            <-signal> <pid>     Send a signal to a process
summary                             Summarize the system
per_cpu         <sym> [<bytes>] [<cpu>]
                                    Display per_cpu variables
grephelp                            Display help on | grep
bp              [<vaddr>]           Set/Display breakpoints
bl              [<vaddr>]           Display breakpoints
bc              <bpnum>             Clear Breakpoint
be              <bpnum>             Enable Breakpoint
bd              <bpnum>             Disable Breakpoint
ss                                  Single Step
dumpcommon                          Common kdb debugging
dumpall                             First line debugging
dumpcpu                             Same as dumpall but only tasks on cpus

示例1(ubuntu启动KGDB)

使用如下环境操作

  • virtual box虚拟机
  • ubuntu22.04Server(服务器)版本作为目标机器(target)
  • ubuntu22.04Server(服务器)版本作为主机调试(host)

target指的是被调试的设备,host设备则指的是监视,和控制调试进程的设备。 注意:因为在调试时希望可以看源码,我们在配置和编译好target源码后复制该虚拟机。

目标调试机器配置

安装系统

可以参考:安装ubuntu-server

准备一些软件

shell
sudo apt install vim net-tools iputils-ping samba make gcc nano

安装源码

配置静态网络

shell
sudo nano /etc/netplan/01-installer-config.yaml

# 配置示例
network: 
	version: 2 
		ethernets: 
			enp0s3: 
			addresses: 
			- 192.168.1.12/24 
		gateway4: 192.168.1.1
		nameservers: 
			addresses: 
				- 8.8.8.8 
				- 8.8.4.4

如果想配置动态:

shell
network: 
	version: 2 
	ethernets: 
		enp0s3: 
			dhcp4:true

下载源码

我们先查看一下内核版本:

shell
uname -r
# 我这里是:
5.15.158

# 搜索一下
apt search linux-source-5.15

只找到一个版本: 版本大概差不多,当然有更匹配的选择更精确。 安装源码:

shell
sudo apt install linux-source-5.15.0

解压

shell
cd /usr/src/linux-source-5.15.0
ls

这里我已经解压后的,实际上只有只有框中的3个文件。解压:

shell
tar -xvf linux-source-5.15.0.tar.bz2
mv linux-source-5.15.0/* ./
rm -r linux-source-5.15.0/

编译源码

我们需要提前安装一些软件,作为环境,具体可以查看上面的流程。

配置

shell
# 复制本系统的配置到源码中
cp -v /boot/config-$(uname -r) .config

# 
make menuconfig

# 配置config,请查看前面的:确定当前内核开启调试

编译与安装

shell
make -j$(npro)
sudo make modules_install
sudo make install
sudo update-grub

之后重启。

调试

复制虚拟机

配置串口

target设置如下: host设置如下: 启动系统时,要想两个串口可以在两个虚拟机中通信,需要先开启未勾选:连接到至现有通道或套接字的系统然后再启动勾选的

测试串口

按照顺序启动两个系统,然后target设备

shell
dmesg |grep tty

#发现log中显示物理串口被加载到ttyS0,我们记住这个

stty -F /dev/ttyS0 speed 115200
sudo cat /dev/ttyS0

然后在host上:

shell
# 同上,查看串口被加载到那个tty上,我这里还是ttyS0

stty -F /dev/ttyS0 speed 115200
sudo su
echo Hello_uart > /dev/ttyS0

查看一些target是否有数据出现。

启动调试

target启动后,我们设置调试串口:

shell
echo "ttyS0" > sys/module/kgdboc/parameters/kgdboc

# 进入调试KGDB
echo g > /proc/sysrq-trigger

当然也可以设置开机就进入调试,不过启动很慢。 target设置如下: 在这里设备就会卡住,我们需要操作host端。 host端设置如下: 这样便进入了KGDB调试。

示例2(嵌入式系统启动KGDB)

这是一个嵌入式的调试过程,使用设备与软件如下:

  • A40i,Kernel-5.10.149, Qt5.10
  • ubuntu+vmware
  • 串口工具

配置

  • 先试用deconfig./build.sh menuconfig开启KGDBDEBUG
  • 然后编译,烧录。

链接调试

在目标机器使用:

shell
#先看看tty,在串口调试输入
#打印如下:
/dev/ttyS0

#我们设置
echo "ttyS0" > /sys/module/kgdboc/parameters/kgdboc
#也可以
echo "ttyS0,115200" > /sys/module/kgdboc/parameters/kgdboc

#然后设置
echo g > /proc/sysrq-trigger

接着会进入: 我们输入help: 这表示可以开始调试了。

Host接入调试

我们打开ubuntu虚拟机,开启后,将串口接入到虚拟机: 通过:

shell
dmesg | grep tty

可以看到ttyUSB0是该调试串口 我们进入到源码目录:

shell
cd SDK_SOURCE
cd out/a40i_h/kernel/build

接着输入(注意sudo):

shell
sudo ../../../toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi-gdb vmlinux

我们设置波特率和串口:

shell
set serial baud 115200
target remote /dev/ttyUSB0

可以看到,当前断点在kgdb_breakpoint;

示例3(调试示例)

^71dde3

^39124e

这里使用的设备是:

  • A40i,Kernel5.10,QT5.10 (target)
  • Ubuntu22 (Host)
  • 串口调试

target先进入KGDB 我们简单的进行调试一个驱动,这里挑选的是i2c,代码以及函数如下:

shell
# SDK_SOURCE/kernel/linux-5.10/drivers/i2c/busses/i2c-sunxi.c
# 选择函数:
sunxi_i2c_status_show

我们阅读源码发现,该函数式通过读取class节点下的status触发进入。 我们在host端设置如下:

shell
(gdb) b sunxi_i2c_status_show
(gdb) c

这样target则会继续执行,我们这样操作target

shell
cd /sys/class/i2c-adapter/i2c-0/device
cat status

执行上面命令后target会卡住。 我们看看host端,发现触发中断了: gdb中输入命令list可以查看一下源码: 对比实际的源码: 一模一样。 好了,我们开始单步调试:

shell
(gdb) n
(gdb) n
(gdb) list

我们查看一下指针i2c的地址,但是: 这里是被优化了,我们来看看i2c_status 我们直接使用:

shell
(gdb) r

target显示: 我们可以print *attr: 或打印print *dev

示例4(调试用户定义模块)

在这里,我们需要编写一个简单的模块,然后进行调试。 这里使用的设备是: +飞凌 A40i,Kernel5.10,QT5.10 (target)

  • Ubuntu22 (Host)
  • 串口调试

编译及加载

在此,我们使用[[lesson_4.c|文件节点操作]]来进行实验,我们先将其放入内核源码进行交叉编译,然后在上传到target设备:

makefile
# makefile修改
#KERNEL_DIR=/SDK_SOUR/OKA40i-linux-sdk/kernel/linux-5.10
CROSS_COMPILE=/SDK_SOUR/OKA40i-linux-sdk/out/toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi-
CC=$(CROSS_COMPILE)gcc
LD=$(CROSS_COMPILE)ld
PWD=$(shell pwd)
ARCH=arm

obj-m := lesson_4.o
module-objs := lesson_4.o

MAKE=make

all:
	$(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(CROSS_COMPILE_PATH) M=$(PWD) modules

.PHONY:clean
clean:
	rm -rf ./*o ./*.ko ./.+ ./*.order ./Module.* ./*.mod.c ./.*.cmd -d ./.\w+

然后载入模块:

shell
insmod lesson_4.ko

准备调试

我们通过以下方式获取模块的加载位置:

获取模块加载地址

方式1

shell
# 方式1
cat /proc/modules
# 结果如下:
lesson_4 16384 0 - Live 0xbf024000 (PO)

方式2

shell
# 方式2
cat /proc/kallsyms | grep lesson_4| sort
# 地址最小的那个就是

方式3

shell
# cd cat /sys/module/<module_name>/sections/
cat /sys/module/lesson_4/sections
ls -all     # 如果是ls,"."开头的文件会隐藏,看不到
cat .text

进入调试

target进入

我们依照^71dde3方法进入调试模式。

Host进入

同样我们依照^71dde3方法进入调试模式。 我们进入驱动模块(文件节点)的源码目录,在里面启动KGDB:

shell
# vmlinux在内核源码的输出目录,我们现需要使用vmlinux进入调试
# SDK_SOURCE/OKA40i-linux-sdk/out/toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi-gdb

# sudo <交叉gdb位置> <内核vmlinx位置>
sudo /home/forlinx/work2/OKA40i-linux-sdk/out/toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi-gdb /home/forlinx/work2/OKA40i-linux-sdk/out/a40i_h/kernel/build/vmlinux

然后设置波特率参数连接TTY(串口),接着载入当前驱动的模块文件:

shell
#地址是上面获取的
add-symbol-file lesson_4.ko 0xbf024000

我们设置一个断点:

shell
(gdb) b scull_write

然后使用tty连接调试设备,并继续运行设备:c 接着在target上操作:

shell
# 驱动这里并没有显示的创建文件节点,所以我们先看看scull设备的主设备号
dmesg

然后再创建一个文件节点:

shell
mknode /dev/scull0 c 241 0

接着:

shell
echo 9 > /dev/scull0

之后就会进入中断,我们可以看看host端的中断状态。

示例5(嵌入式系统开机启动KGDB)

上面演示的都是开机后使用sys Rq进入的调试,但如果我们需要调试开机过程中的代码,则需要在开机后就进入KGDB

  • RK3588 kernel-6.1.75
  • ubuntu-22.0

注意ARM设备似乎无法在更早的时候进入KGDB,而X86可以更早进入KGDB,所以RK3588无法调试比较早的代码,具体原因可以查看Early_KGDB

配置

c
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF5=y
CONFIG_KGDB=y 
CONFIG_KGDB_SERIAL_CONSOLE=y
CONFIG_KGDB_KDB=y
# 记得关闭优化,不然很多局部变量都会被优化掉,调试查看不到任何数值
# 1. 进行优化,同时保留调试信息
# 2. 关闭减少调试信息选项
CONFIG_CC_OPTIMIZE_FOR_DEBUGGING=y
CONFIG_DEBUG_INFO_REDUCED=n

这里的CONFIG_KGDB_KDB是可选选项,它的作用是,在你进入KGDB中断时(错误,主动进入),你可以通过串口命令行进行基本的调试。

启动

我们可以将启动命令行加入到开机command line,可以通过以下方式:

  • uboot 命令行
shell
setenv bootargs ${bootargs} kgdboc_earlycon=uart kgdbwait
saveenv
boot
  • 设备树
shell
# 注意记得将原来的bootargs,完全复制过来,再加上kgdboc_earlycon=uart kgdbwait
&chosen {
	bootargs = "kgdboc_earlycon=uart kgdbwait earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 irqchip.gicv3_pseudo_nmi=0 root=PARTUUID=614e0000-0000 rw rootwait";
};

这样我们就可以在开机时进入KGDB 接下来的调试过程可以参考上面的示例。 注意:这里的启动命令行与上面的kgdboc=ttyXXXX,115200 kgdbwait不一样,这是因为在开机初期,TTY模块还未加载,只有基础的uart,所以如果RK3588进入系统后,还是可以像上面一样使用kgdboc=ttyXXXX,115200 kgdbwait进入KGDB模式。

其他

当然我们也可以通过在代码中注入kgdb_breakpoint();,比如在按键中加入该中断:

贡献者

The avatar of contributor named as Px Px

页面历史

撰写